// import com.alibaba.druid.sql.ast.SQLExpr;
// import com.alibaba.druid.sql.ast.SQLObject;
// import com.alibaba.druid.sql.ast.SQLSetQuantifier;
// import com.alibaba.druid.sql.ast.expr.SQLIdentifierExpr;
// import com.alibaba.druid.sql.ast.expr.SQLLiteralExpr;
// import com.alibaba.druid.sql.ast.expr.SQLVariantRefExpr;
// import com.alibaba.druid.sql.ast.statement.*;
// import com.alibaba.druid.sql.dialect.mysql.ast.*;
// import com.alibaba.druid.sql.dialect.mysql.ast.clause.MySqlSelectIntoStatement;
// import com.alibaba.druid.sql.dialect.mysql.ast.expr.MySqlOutFileExpr;
// import com.alibaba.druid.sql.dialect.mysql.ast.statement.MySqlSelectQueryBlock;
// import com.alibaba.druid.sql.parser.ParserException;
// import com.alibaba.druid.sql.parser.SQLExprParser_ptr;
// import com.alibaba.druid.sql.parser.SQLSelectParser;
// import com.alibaba.druid.sql.parser.Token;
// import com.alibaba.druid.util.FnvHash;

// import java.util.ArrayList;
// import java.util.List;

#include "MySqlSelectIntoParser.h"
#include "MySqlExprParser.h"
#include "../ast/clause/MySqlSelectIntoStatement.h"
#include "../ast/statement/MySqlSelectQueryBlock.h"
#include "../ast/expr/MySqlOutFileExpr.h"
#include "../ast/MySqlIndexHintImpl.h"
#include "../ast/MySqlUseIndexHint.h"
#include "../ast/MySqlIgnoreIndexHint.h"
#include "../ast/MySqlForceIndexHint.h"
#include "../../../ast/SQLSetQuantifier.h"
#include "../../../../utils/instanceof.h"
#include "../../../../utils/FnvHash.h"

MySqlSelectIntoParser::MySqlSelectIntoParser(SQLExprParser_ptr exprParser)
    : SQLSelectParser(exprParser)
{
  argsList = std::make_shared<std::list<SQLExpr_ptr>>();
}

MySqlSelectIntoParser::MySqlSelectIntoParser(string_ptr sql)
    : MySqlSelectIntoParser(MySqlExprParser_ptr(new MySqlExprParser(sql)))
{
}

MySqlSelectIntoStatement_ptr MySqlSelectIntoParser::parseSelectInto()
{
  SQLSelect_ptr select_ = select();
  MySqlSelectIntoStatement_ptr stmt = MySqlSelectIntoStatement_ptr(new MySqlSelectIntoStatement());
  stmt->setSelect(select_);
  stmt->setVarList(argsList);
  return stmt;
}

// @Override
SQLSelectQuery_ptr MySqlSelectIntoParser::query(SQLObject_ptr parent, BOOL_ptr acceptUnion)
{
  if (lexer->token()->name->c_str() == Token::LPAREN.name->c_str())
  {
    lexer->nextToken();

    SQLSelectQuery_ptr select = SQLSelectParser::query();
    accept(Token::RPAREN);

    return queryRest(select, acceptUnion);
  }

  MySqlSelectQueryBlock_ptr queryBlock = MySqlSelectQueryBlock_ptr(new MySqlSelectQueryBlock());

  if (lexer->token()->name->c_str() == Token::SELECT.name->c_str())
  {
    lexer->nextToken();

    if (lexer->token()->name->c_str() == Token::HINT.name->c_str())
    {
      this->exprParser->parseHints(queryBlock->getHints());
    }

    if (lexer->token()->name->c_str() == Token::COMMENT.name->c_str())
    {
      lexer->nextToken();
    }

    if (lexer->token()->name->c_str() == (Token::DISTINCT.name->c_str()))
    {
      queryBlock->setDistionOption(SQLSetQuantifier::DISTINCT);
      lexer->nextToken();
    }
    else if (lexer->identifierEquals(make_string_ptr("DISTINCTROW")))
    {
      queryBlock->setDistionOption(SQLSetQuantifier::DISTINCTROW);
      lexer->nextToken();
    }
    else if (lexer->token()->name->c_str() == (Token::ALL.name->c_str()))
    {
      queryBlock->setDistionOption(SQLSetQuantifier::ALL);
      lexer->nextToken();
    }

    if (lexer->identifierEquals(make_string_ptr("HIGH_PRIORITY")))
    {
      queryBlock->setHignPriority(BOOL::TRUE);
      lexer->nextToken();
    }

    if (lexer->identifierEquals(make_string_ptr("STRAIGHT_JOIN")))
    {
      queryBlock->setStraightJoin(BOOL::TRUE);
      lexer->nextToken();
    }

    if (lexer->identifierEquals(make_string_ptr("SQL_SMALL_RESULT")))
    {
      queryBlock->setSmallResult(BOOL::TRUE);
      lexer->nextToken();
    }

    if (lexer->identifierEquals(make_string_ptr("SQL_BIG_RESULT")))
    {
      queryBlock->setBigResult(BOOL::TRUE);
      lexer->nextToken();
    }

    if (lexer->identifierEquals(make_string_ptr("SQL_BUFFER_RESULT")))
    {
      queryBlock->setBufferResult(BOOL::TRUE);
      lexer->nextToken();
    }

    if (lexer->identifierEquals(make_string_ptr("SQL_CACHE")))
    {
      queryBlock->setCache(BOOL::TRUE);
      lexer->nextToken();
    }

    if (lexer->identifierEquals(make_string_ptr("SQL_NO_CACHE")))
    {
      queryBlock->setCache(BOOL::FALSE);
      lexer->nextToken();
    }

    if (lexer->identifierEquals(make_string_ptr("SQL_CALC_FOUND_ROWS")))
    {
      queryBlock->setCalcFoundRows(BOOL::TRUE);
      lexer->nextToken();
    }

    parseSelectList(queryBlock);

    argsList = parseIntoArgs();
  }

  parseFrom(queryBlock);

  parseWhere(queryBlock);

  parseGroupBy(queryBlock);

  queryBlock->setOrderBy(this->exprParser->parseOrderBy());

  if (lexer->token()->name->c_str() == Token::LIMIT.name->c_str())
  {
    queryBlock->setLimit(this->exprParser->parseLimit());
  }

  if (lexer->token()->name->c_str() == Token::PROCEDURE.name->c_str())
  {
    lexer->nextToken();
    std::string tmp = "TODO. ";
    tmp += encode_util::UnicodeToUTF8(lexer->info());
    throw new ParserException(make_string_ptr(tmp));
  }

  parseInto(queryBlock);

  if (lexer->token()->name->c_str() == Token::FOR.name->c_str())
  {
    lexer->nextToken();
    accept(Token::UPDATE);

    queryBlock->setForUpdate(BOOL::TRUE);
  }

  if (lexer->token()->name->c_str() == Token::LOCK.name->c_str())
  {
    lexer->nextToken();
    accept(Token::IN);
    acceptIdentifier("SHARE");
    acceptIdentifier("MODE");
    queryBlock->setLockInShareMode(BOOL::TRUE);
  }

  return queryRest(queryBlock, acceptUnion);
}

/**
 * parser the select into arguments
 *
 * @return
 */
SQLExpr_list_ptr MySqlSelectIntoParser::parseIntoArgs()
{
  SQLExpr_list_ptr args = std::make_shared<std::list<SQLExpr_ptr>>();
  if (lexer->token()->name->c_str() == (Token::INTO.name->c_str()))
  {
    accept(Token::INTO);
    // lexer->nextToken();
    for (;;)
    {
      SQLExpr_ptr var = exprParser->primary();
      if (instanceof <SQLExpr, SQLIdentifierExpr>(var))
      {
        var = SQLVariantRefExpr_ptr(new SQLVariantRefExpr((std::dynamic_pointer_cast<SQLIdentifierExpr>(var))->getName()));
      }
      args->push_back(var);
      if (lexer->token()->name->c_str() == Token::COMMA.name->c_str())
      {
        accept(Token::COMMA);
        continue;
      }
      else
      {
        break;
      }
    }
  }
  return args;
}

void MySqlSelectIntoParser::parseInto(SQLSelectQueryBlock_ptr queryBlock)
{
  if (lexer->token()->name->c_str() == (Token::INTO.name->c_str()))
  {
    lexer->nextToken();

    if (lexer->identifierEquals(make_string_ptr("OUTFILE")))
    {
      lexer->nextToken();

      MySqlOutFileExpr_ptr outFile = MySqlOutFileExpr_ptr(new MySqlOutFileExpr());
      outFile->setFile(expr());

      queryBlock->setInto(outFile);

      if (lexer->identifierEquals(make_string_ptr("FIELDS")) || lexer->identifierEquals(make_string_ptr("COLUMNS")))
      {
        lexer->nextToken();

        if (lexer->identifierEquals(make_string_ptr("TERMINATED")))
        {
          lexer->nextToken();
          accept(Token::BY);
        }
        outFile->setColumnsTerminatedBy(std::dynamic_pointer_cast<SQLLiteralExpr>(expr()));

        if (lexer->identifierEquals(make_string_ptr("OPTIONALLY")))
        {
          lexer->nextToken();
          outFile->setColumnsEnclosedOptionally(BOOL::TRUE);
        }

        if (lexer->identifierEquals(make_string_ptr("ENCLOSED")))
        {
          lexer->nextToken();
          accept(Token::BY);
          outFile->setColumnsEnclosedBy(std::dynamic_pointer_cast<SQLLiteralExpr>(expr()));
        }

        if (lexer->identifierEquals(make_string_ptr("ESCAPED")))
        {
          lexer->nextToken();
          accept(Token::BY);
          outFile->setColumnsEscaped(std::dynamic_pointer_cast<SQLLiteralExpr>(expr()));
        }
      }

      if (lexer->identifierEquals(make_string_ptr("LINES")))
      {
        lexer->nextToken();

        if (lexer->identifierEquals(make_string_ptr("STARTING")))
        {
          lexer->nextToken();
          accept(Token::BY);
          outFile->setLinesStartingBy(std::dynamic_pointer_cast<SQLLiteralExpr>(expr()));
        }
        else
        {
          lexer->identifierEquals(make_string_ptr("TERMINATED"));
          lexer->nextToken();
          accept(Token::BY);
          outFile->setLinesTerminatedBy(std::dynamic_pointer_cast<SQLLiteralExpr>(expr()));
        }
      }
    }
    else
    {
      queryBlock->setInto(this->exprParser->name());
    }
  }
}

SQLTableSource_ptr MySqlSelectIntoParser::parseTableSourceRest(SQLTableSource_ptr tableSource)
{
  if (lexer->identifierEquals(make_string_ptr("USING")))
  {
    return tableSource;
  }

  parseIndexHintList(tableSource);

  return SQLSelectParser::parseTableSourceRest(tableSource);
}

void MySqlSelectIntoParser::parseIndexHintList(SQLTableSource_ptr tableSource)
{
  if (lexer->token()->name->c_str() == Token::USE.name->c_str())
  {
    lexer->nextToken();
    MySqlUseIndexHint_ptr hint = MySqlUseIndexHint_ptr(new MySqlUseIndexHint());
    parseIndexHint(hint);
    tableSource->getHints()->push_back(hint);
    parseIndexHintList(tableSource);
  }

  if (lexer->identifierEquals(make_string_ptr("IGNORE")))
  {
    lexer->nextToken();
    MySqlIgnoreIndexHint_ptr hint = MySqlIgnoreIndexHint_ptr(new MySqlIgnoreIndexHint());
    parseIndexHint(hint);
    tableSource->getHints()->push_back(hint);
    parseIndexHintList(tableSource);
  }

  if (lexer->identifierEquals(FnvHash::Constants::FORCE))
  {
    lexer->nextToken();
    MySqlForceIndexHint_ptr hint = MySqlForceIndexHint_ptr(new MySqlForceIndexHint());
    parseIndexHint(hint);
    tableSource->getHints()->push_back(hint);
    parseIndexHintList(tableSource);
  }
}

void MySqlSelectIntoParser::parseIndexHint(MySqlIndexHintImpl_ptr hint)
{
  if (lexer->token()->name->c_str() == Token::INDEX.name->c_str())
  {
    lexer->nextToken();
  }
  else
  {
    accept(Token::KEY);
  }

  if (lexer->token()->name->c_str() == Token::FOR.name->c_str())
  {
    lexer->nextToken();

    if (lexer->token()->name->c_str() == Token::JOIN.name->c_str())
    {
      lexer->nextToken();
      hint->setOption(MySqlIndexHint_Option::JOIN);
    }
    else if (lexer->token()->name->c_str() == Token::ORDER.name->c_str())
    {
      lexer->nextToken();
      accept(Token::BY);
      hint->setOption(MySqlIndexHint_Option::ORDER_BY);
    }
    else
    {
      accept(Token::GROUP);
      accept(Token::BY);
      hint->setOption(MySqlIndexHint_Option::GROUP_BY);
    }
  }

  accept(Token::LPAREN);
  if (lexer->token()->name->c_str() == Token::PRIMARY.name->c_str())
  {
    lexer->nextToken();
    hint->getIndexList()->push_back(SQLIdentifierExpr_ptr(new SQLIdentifierExpr(make_string_ptr("PRIMARY"))));
  }
  else
  {
    this->exprParser->names(hint->getIndexList());
  }
  accept(Token::RPAREN);
}

SQLUnionQuery_ptr MySqlSelectIntoParser::unionRest(SQLUnionQuery_ptr union_)
{
  if (lexer->token()->name->c_str() == Token::LIMIT.name->c_str())
  {
    union_->setLimit(this->exprParser->parseLimit());
  }
  return SQLSelectParser::unionRest(union_);
}

MySqlExprParser_ptr MySqlSelectIntoParser::getExprParser()
{
  return std::dynamic_pointer_cast<MySqlExprParser>(exprParser);
}
